Let’s load the packages we will need

library(data.table)
library(dplyr)
library(tidyr)
library(ggplot2)
library(patchwork)
library(ggsci)
library(scales)
library(ggpubr)
library(knitr)
library(tibble)
library(Biostrings)
library(msa)
library(tibble)
library(openxlsx)



Then read in our NextStrain metadata file, protein alignment file, and generate necessary data frames for downstream analyses

# Read in NextStrain subsampled metadata file
subsampled <- fread("./nextstrain_data/metadata_subsampled.tsv", header=T)

nextstrain_clades <- unique(subsampled$Nextstrain_clade)
nonhuman_strains <- subsampled %>% 
  filter(host!="Homo sapiens") %>% 
  pull(genbank_accession)

# Read in nucleocapsid multiple sequence alignment (MSA)
# Protein sequences were obtained from NCBI GenBank using accessions in NextStrain metadata file, and then aligned with MUSCLE
# In our MSA of 419AA, 3 columns removed from MSA, and X replaced with gaps

mymsa <- readAAMultipleAlignment("./N_alignment/nucleocapsid_extract_419aa_clean_gaps.aln", format="fasta")
mymsa <- unmasked(mymsa)
mymsa
AAStringSet object of length 3270:
       width seq                                                                              names               
   [1]   419 MSDNGPQNQRNAPRITFGGPSDSTGSNQNGERSGARSKQ...LPQRQKKQQTVTLLPAADLDDFSKQLQQSMSSADSTQA MN908947
   [2]   419 ---------RNAPRITFGGPSDSTGSNQNGE--------...----Q--------LPAA--------------------- OK161423
   [3]   419 MSDNGPQNQRNAPRITFGGPSDSTGSNQNGERSGARSKQ...-------------------------------------- OM090132
   [4]   419 MSDNGPQNQRNAPRITFGGPSDSTGSNQNGERSGARSKQ...LPQRQKKQQTVTLLPAADLDDFSKQLQQSMSSADSTQA OL348698
   [5]   419 MSDNGPQNQRNAPRITFGGPSDSTGSNQNGERSGARSKQ...LPQRQKKQQTVTLLPAADLDDFSKQLQQSMSSADSTQA OL352022
   ...   ... ...
[3266]   419 MSDNGPQNQRNALRITFGGPSDSTGSNQNGERSGARSKQ...LPQRQKKQQTVTLLPAADLDDFSKQLQQSMSSADSTQA OL623678
[3267]   419 MSDNGPQNQRNALRITFGGPSDSTGSNQNGERSGARSKQ...LPQRQKKQQTVTLLPAADLDDFSKQLQQSMSSADSTQA OL623732
[3268]   419 MSDNGPQNQRNALRITFGGPSDSTGSNQNGERSGARSKQ...LPQRQKKQQTVTLLPAADLDDFSKQLQQSMSSADSTQA OL623819
[3269]   419 MSDNGPQNQRNALRITFGGPSDSTGSNQNGERSGARSKQ...LPQRQKKQQTVTLLPAADLDDFSKQLQQSMSSADSTQA MZ275287
[3270]   419 MSDNGPQNQRNAPRITFGGPSDSTGSNQNGERSGARSKQ...LPQRQKKQQTVTLLPAADLDDFSKQLQQSMSSADSTQA MZ544366
# If we look at the MSA we will notice that some sequences are composed of many gaps
# Let's remove genomes/sequences where there are more than X% gaps (let's set 10% for example) in the nucleocapsid protein, as well as sequences with non-human hosts

check_gaps <- function(seq, threshold) {
  out <- alphabetFrequency(mymsa[[seq]])[["-"]]/419 > threshold
  out
}
myres <- lapply(1:3270, check_gaps, threshold=0.1)  ## change gap threshold here
       
pass <- which(sapply(myres, function(x) !isTRUE(x)))  ## these are sequences passing our gap threshold
mymsa <- mymsa[pass]  ## update our MSA, removing highly gapped sequences
mymsa <- mymsa[setdiff(names(mymsa), nonhuman_strains)]  ## remove non-human sequences
genomes_present_msa <- names(mymsa)  ## get a list of all genomes in our filtered MSA

# Check how many sequences we have in our filtered dataset
length(genomes_present_msa)
[1] 3103
# Set accession of Wuhan-Hu-1 as reference sequence in MSA
ref <- mymsa["MN908947"]

# Generate a filtered metadata df from the original, which we will be using later on
metadata_filtered <- subsampled %>% 
  filter(genbank_accession %in% genomes_present_msa)
head(select(metadata_filtered, strain, genbank_accession, Nextstrain_clade, pango_lineage))
                                         
# Let's look at the distribution of variant genomes in our filtered dataset
metadata_filtered %>% 
  group_by(Nextstrain_clade) %>%
  summarize(count_genomes=n_distinct(genbank_accession)) %>% 
  ggplot(aes(x=reorder(Nextstrain_clade, count_genomes), y=count_genomes)) +
  geom_bar(stat="identity", fill="dark red") +
  geom_text(aes(label=count_genomes), hjust=0, size=2.8) +
  xlab("Variant") +
  ylab("Number of sequences") +
  theme_minimal() +
  coord_flip()


# Define domains within the nucleocapsid to help us identify where mutations are occurring
# Domain positions obtained from Yang et al. (2021) 
# https://www.frontiersin.org/articles/10.3389/fchem.2020.624765/full
domains_df <- data.frame(c(1:419))
names(domains_df) <- "msa_position"
domains_df <- domains_df %>%
  mutate(domain = case_when(msa_position>=1 & msa_position<=40 ~ "NTD",
                            msa_position>=41 & msa_position<=173 ~ "RBD",
                            msa_position>=174 & msa_position<=249 ~ "LINK",
                            msa_position>=250 & msa_position<=364 ~ "dimerization",
                            msa_position>=365 ~ "CTD",
                            TRUE ~ "NA"))
domains <- c("NTD", "RBD", "LINK", "dimerization", "CTD")



Get frequencies of all genotypes (per column summary) for each variant relative to Wuhan-Hu-1

# Let's transfer the reference sequence info into a data frame
ref_df <- as.matrix(mymsa["MN908947"]) %>% t()
ref_df <- ref_df[, 1] %>% 
  as.data.frame(check.names=F) %>% 
  rowid_to_column(var="msa_position") %>%
  dplyr::rename("residue_reference" = ".")


# Function to identify major residues per MSA column per lineage
get_genotypes <- function(myvariant, mydf) {
  genomes <- filter(metadata_filtered, Nextstrain_clade==myvariant) %>% pull(genbank_accession)
  subaln <- mymsa[genomes] ##filter to only sequences present in MSA
  ncols <- length(subaln[[1]])
  mat <- as.matrix(subaln)  ## convert MSA into matrix
 
  
  ## We will use a nested function here to get stats of all MSA columns
  ## This part needs to be optimized
  get_colstats <- function(mypos, mysubaln) {
    table <- prop.table(table(mat[, mypos])) %>%
      round(digits=3) %>% 
      as.data.frame.table() %>% 
      dplyr::rename(residue=Var1, freq=Freq) %>%
      mutate(msa_position=mypos, variant=myvariant) %>%
      left_join(domains_df, by="msa_position") %>% 
      left_join(ref_df, by="msa_position") %>% 
      relocate(msa_position)
    table
  }
  
  results <- rbindlist(lapply(1:ncols, get_colstats, mysubaln=subaln))
  results
}

mycolstats_df <- rbindlist(lapply(nextstrain_clades, get_genotypes, mydf=metadata_filtered))
mycolstats_df$domain <- factor(mycolstats_df$domain, levels=domains)



Now that we have our information in a data frame, we can manipulate this to help us in making some plots

#    Let's make a new data frame from that with the following information:
#      i)    whether the residue is a mutation
#      ii)   whether we want to keep the residue for plotting (set freq threshold here, eg. present in at least 2% of genome population- applied but not used for now)
#      iii)  the proportion of genomes with mutations for a given variant population

threshold <- 0.02
mycolstats_df <- mycolstats_df %>% 
  mutate(mutation = case_when(residue == "-" ~ "NA", 
                              residue == residue_reference ~ "no", 
                              TRUE ~ "yes"),
                              keep = case_when(freq >=threshold & mutation == "yes" ~ "yes", TRUE ~ "no"))


residues_df <- mycolstats_df %>% 
  group_by(variant, msa_position, domain) %>% 
  summarize(prop_unchanged=sum(freq[mutation=="no"]), 
            prop_missing=sum(freq[mutation=="NA"]), 
            prop_mutation=1-prop_unchanged-prop_missing)
`summarise()` has grouped output by 'variant', 'msa_position'. You can override using the `.groups` argument.
    
# We can now visualize where mutations are occurring for the different variants
residues_df %>% 
  ggplot(aes(x=msa_position, y=prop_mutation)) + 
  geom_point(size=0.5, aes(color=domain)) +
  scale_fill_npg() +
  scale_color_npg() +
  geom_hline(yintercept=threshold, linetype="twodash") +
  geom_vline(xintercept=c(40, 173, 249, 364, 419), linetype="dotdash", color="gray") +
  xlab("Residue (position)") +
  ylab("Proportion of genomes with mutations") +
  theme(axis.text=element_text(size=8), axis.title=element_text(size=10), plot.title=element_text(hjust=0.5)) +
  facet_wrap(.~variant, nrow=6) +
  ggtitle("Mutation frequencies across nucleocapsid domains of SARS-CoV-2 variants")


# In the plot above, the x-axis is the residue/position in the MSA and the y-axis is the proportion of genomes within a variant with mutations (compared to Wuhan-Hu-1)

# What about the domains where mutations most commonly occur? This is a simplified view of the previous plot
domain_mutations_df <- residues_df %>%
  group_by(variant, domain) %>% 
  summarize(no_mutations=n_distinct(msa_position[prop_mutation<threshold])/n(), with_mutations=1-no_mutations) %>%
  reshape2::melt(id.vars=c("variant", "domain"), value.name="prop") 
`summarise()` has grouped output by 'variant'. You can override using the `.groups` argument.
  
domain_mutations_df %>%
  ggplot(aes(x=domain, y=prop)) + 
  geom_bar(stat="identity", aes(fill=variable)) + 
  scale_fill_manual(values=c("#D2F0EE", "#D21F3C"), name="") + 
  theme(axis.text=element_text(size=8), axis.title=element_text(size=10), plot.title=element_text(hjust=0.5)) +
  scale_x_discrete(limits = rev) +
  xlab("") +
  ylab("Proportion of genomes") +
  facet_wrap(.~variant, nrow=6) +
  coord_flip() +
  ggtitle("Mutation frequencies across nucleocapsid domains of SARS-CoV-2 variants")


# For a list of positions and mutation frequencies please see Excel sheet



Let’s get an idea of what mutations are present (considering only the consensus sequence for each variant)

# Let's look at only positions with mutations
# First let's get a MSA consensus sequence for each variant

get_conseq <- function(myvariant) {
  # first filter MSA by selecting genomes corresponding to variant
  genomes <- filter(metadata_filtered, Nextstrain_clade==myvariant) %>% pull(genbank_accession)
  alnkeep <- AAMultipleAlignment(mymsa[genomes])
  conseq <- strsplit(msaConsensusSequence(alnkeep, ignoreGaps=T), "") %>%
    unlist() %>% 
    as.data.frame() %>% 
    mutate(variant=myvariant) %>%
    dplyr::rename("residue" = ".") %>% 
    rowid_to_column(var="msa_position")
  conseq
}

conseq_df <- rbindlist(lapply(nextstrain_clades, get_conseq))
conseq_df <- conseq_df %>%
  left_join(ref_df) %>%
  left_join(domains_df) %>% 
  mutate(mutation = case_when(residue == "-" ~ "NA",
                              residue != residue_reference ~ "yes",
                              TRUE ~ "no"))
Joining, by = "msa_position"
Joining, by = "msa_position"
                            
# Only plot positions with mutations
positions2plot <- conseq_df %>% 
  filter(mutation=="yes" | mutation=="NA") %>% 
  pull(msa_position)

# Set msa position column as factors                           
conseq_df$msa_position <- factor(conseq_df$msa_position, levels=unique(conseq_df$msa_position))
conseq_df$mutation <- factor(conseq_df$mutation, levels=c("yes", "no", "NA"))
conseq_df$domain <- factor(conseq_df$domain, levels=domains)

# Plot
conseq_df %>% 
  filter(msa_position %in% positions2plot) %>% 
  ggplot(aes(x=msa_position, y=variant, fill=mutation)) +
  geom_tile(color="white", size=0.2) +
  geom_text(aes(label=residue), size=2.5) +
  scale_fill_manual(values=c("#D21F3C", "#D2F0EE","#D4D4D4")) +
  theme(axis.text.x=element_text(angle=90), plot.title=element_text(hjust=0.5)) + 
  xlab("Residue position") +
  ylab("Variant") +
  facet_wrap(.~domain, nrow=1, scales="free_x") +
  ggtitle("Mutations in nucleocapsid across consensus sequence of SARS-CoV-2 variants")


# The dimerization domain does not have mutations according to the 'consensus sequence' of each variant



Next let’s look at individual genotypes for our variants of interest

# We can define which variant we want to look at (eg. VOCs or all variants with at least 10 genome representatives)
# Let's look at the alpha, beta, gamma, delta, and omicron variants for now

plot_genotype_mutations <- function(myvariant) {
  genomes <- filter(metadata_filtered, Nextstrain_clade==myvariant) %>% pull(genbank_accession)
  alnkeep <- as.matrix(unique(mymsa[genomes])) %>%
    t() %>% 
    as.data.frame() %>% 
    rowid_to_column(var="msa_position")
  
  # force reference sequence to be in dataset
  alnref <- as.matrix(mymsa["MN908947"]) %>%
    t() %>% 
    as.data.frame() %>% 
    rowid_to_column(var="msa_position")
  
  # add ref only if it's not already present
  if (!"MN908947" %in% names(alnkeep)) {
    myaln <- left_join(alnkeep, alnref, by="msa_position")
  } else {
    myaln <- alnkeep
  }
  
  myplotdf <- myaln %>% 
    #filter(msa_position %in% positions2plot) %>% 
    reshape2::melt(id.var="msa_position", variable.name="genome", value.name="residue") %>% 
    left_join(ref_df, by="msa_position") %>%
    left_join(domains_df, by="msa_position") %>%
    mutate(mutation=case_when(residue=="-" ~ "NA",
                              residue!=residue_reference ~ "yes",
                              residue==residue_reference ~ "no",
                              TRUE ~ "error")) 
  
  # Rename reference sequence
  myplotdf$genome <- gsub("MN908947", "Wuhan-Hu-1", myplotdf$genome)
  
  myplotdf$domain <- factor(myplotdf$domain, levels=domains)
  myplotdf$msa_position <- factor(myplotdf$msa_position, levels=unique(myplotdf$msa_position))
  myplotdf$mutation <- factor(myplotdf$mutation, levels=c("yes", "no", "NA"))
  
  positions2plot <- myplotdf %>% 
    filter(mutation=="yes" | mutation=="NA") %>% 
    distinct(msa_position) %>% 
    pull(msa_position)
  
  myplot <- myplotdf %>%
    filter(msa_position %in% positions2plot) %>% 
    ggplot(aes(x=msa_position, y=genome, fill=mutation)) +
    geom_tile(color="white", size=0.2) +
    geom_text(aes(label=residue), size=2.5) +
    scale_fill_manual(values=c("#D21F3C", "#D2F0EE","#D4D4D4")) +
    theme(axis.text.x=element_text(angle=90, size=7), plot.title=element_text(hjust=0.5), axis.text.y=element_text(size=5)) + 
    xlab("Residue position") +
    ylab("Genome") +
    facet_wrap(.~domain, nrow=1, scales="free_x") +
    ggtitle(paste(myvariant, " genotypes", sep=""))
  
  myplot
 
}

# Run function on our variants of interest
voi <- c("20I (Alpha, V1)", "20H (Beta, V2)", "20J (Gamma, V3)", "21A (Delta)", "21I (Delta)", "21K (Omicron)")
genotype_plots <- lapply(voi, plot_genotype_mutations)
genotype_plots[[1]]

genotype_plots[[2]] + genotype_plots[[3]] 

genotype_plots[[4]]

genotype_plots[[5]] 

genotype_plots[[6]]



Write mutations to a file

print_mutations <- function(myvariant, myoption) {
  if (myvariant == "all") {
    subaln <- mymsa
    
  } else {
    genomes <- filter(metadata_filtered, Nextstrain_clade==myvariant) %>% pull(genbank_accession)
    subaln <- mymsa[genomes] ##filter to only sequences present in MSA
  }
 
  ncols <- length(subaln[[1]])
  mat <- as.matrix(subaln)  ## convert MSA into matrix
  
  ## This part needs to be optimized
  get_colstats <- function(mypos, mysubaln) {
    table <- table(mat[, mypos]) %>%
      as.data.frame.table() %>% 
      dplyr::rename(residue=Var1, freq=Freq) %>%
      mutate(msa_position=mypos, variant=myvariant) %>%
      left_join(domains_df, by="msa_position") %>% 
      left_join(ref_df, by="msa_position") %>% 
      relocate(msa_position)
    table
  }
  
  results <- rbindlist(lapply(1:ncols, get_colstats, mysubaln=subaln))
  
  if (myoption == "no") {
    results <- results %>% group_by(msa_position, domain) %>%
      summarize(count_mutations=sum(freq[residue!=residue_reference & residue!="-"]), 
              count_conserved=sum(freq[residue==residue_reference | residue=="-"])) %>% 
      mutate(variant=myvariant, total=sum(count_mutations, count_conserved),
             mutation_percentage=count_mutations/total*100, conserved_percentage=count_conserved/total*100) %>%
      select(-total)
    
  } else if (myoption == "yes") {
    
    results <- results %>% group_by(msa_position, domain, residue, residue_reference) %>%
    summarize(count_mutations=sum(freq[residue!=residue_reference & residue!="-"]), 
              count_conserved=sum(freq[residue==residue_reference | residue=="-"])) %>%
    ungroup() %>%
    group_by(msa_position, domain) %>%
    mutate(variant=myvariant, total=sum(count_mutations, count_conserved), mutation_percentage=count_mutations/total*100, conserved_percentage=count_conserved/total*100) %>%
    select(-total)
  }

  results
}

mutations_variant_noresidue <- rbindlist(lapply(nextstrain_clades, print_mutations, myoption="no"))
`summarise()` has grouped output by 'msa_position'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'msa_position'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'msa_position'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'msa_position'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'msa_position'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'msa_position'. You can override using the `.groups` argument.
`summarise()` has grouped output by 'msa_position'. You can override using the `.groups` argument.
LS0tCnRpdGxlOiAiRXhwbG9yaW5nIHRoZSBTQVJTLUNvVi0yIG51Y2xlb2NhcHNpZCBwcm90ZWluIgphdXRob3I6ICJTb28gSmVuIExvdyIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKLS0tCjxicj4KPGJyPgogCiMjIyMgTGV0J3MgbG9hZCB0aGUgcGFja2FnZXMgd2Ugd2lsbCBuZWVkCmBgYHtyIGxvYWQtbGlicmFyaWVzLCB3YXJuaW5ncz1GLCBtZXNzYWdlPUZ9CmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShnZ3NjaSkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoZ2dwdWJyKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KHRpYmJsZSkKbGlicmFyeShCaW9zdHJpbmdzKQpsaWJyYXJ5KG1zYSkKbGlicmFyeSh0aWJibGUpCmxpYnJhcnkob3Blbnhsc3gpCmBgYAoKPGJyPgo8YnI+CgojIyMjIFRoZW4gcmVhZCBpbiBvdXIgTmV4dFN0cmFpbiBtZXRhZGF0YSBmaWxlLCBwcm90ZWluIGFsaWdubWVudCBmaWxlLCBhbmQgZ2VuZXJhdGUgbmVjZXNzYXJ5IGRhdGEgZnJhbWVzIGZvciBkb3duc3RyZWFtIGFuYWx5c2VzCmBgYHtyIHJlYWQtZmlsZXN9CiMgUmVhZCBpbiBOZXh0U3RyYWluIHN1YnNhbXBsZWQgbWV0YWRhdGEgZmlsZQpzdWJzYW1wbGVkIDwtIGZyZWFkKCIuL25leHRzdHJhaW5fZGF0YS9tZXRhZGF0YV9zdWJzYW1wbGVkLnRzdiIsIGhlYWRlcj1UKQoKbmV4dHN0cmFpbl9jbGFkZXMgPC0gdW5pcXVlKHN1YnNhbXBsZWQkTmV4dHN0cmFpbl9jbGFkZSkKbm9uaHVtYW5fc3RyYWlucyA8LSBzdWJzYW1wbGVkICU+JSAKICBmaWx0ZXIoaG9zdCE9IkhvbW8gc2FwaWVucyIpICU+JSAKICBwdWxsKGdlbmJhbmtfYWNjZXNzaW9uKQoKIyBSZWFkIGluIG51Y2xlb2NhcHNpZCBtdWx0aXBsZSBzZXF1ZW5jZSBhbGlnbm1lbnQgKE1TQSkKIyBQcm90ZWluIHNlcXVlbmNlcyB3ZXJlIG9idGFpbmVkIGZyb20gTkNCSSBHZW5CYW5rIHVzaW5nIGFjY2Vzc2lvbnMgaW4gTmV4dFN0cmFpbiBtZXRhZGF0YSBmaWxlLCBhbmQgdGhlbiBhbGlnbmVkIHdpdGggTVVTQ0xFCiMgSW4gb3VyIE1TQSBvZiA0MTlBQSwgMyBjb2x1bW5zIHJlbW92ZWQgZnJvbSBNU0EsIGFuZCBYIHJlcGxhY2VkIHdpdGggZ2FwcwoKbXltc2EgPC0gcmVhZEFBTXVsdGlwbGVBbGlnbm1lbnQoIi4vTl9hbGlnbm1lbnQvbnVjbGVvY2Fwc2lkX2V4dHJhY3RfNDE5YWFfY2xlYW5fZ2Fwcy5hbG4iLCBmb3JtYXQ9ImZhc3RhIikKbXltc2EgPC0gdW5tYXNrZWQobXltc2EpCm15bXNhCgojIElmIHdlIGxvb2sgYXQgdGhlIE1TQSB3ZSB3aWxsIG5vdGljZSB0aGF0IHNvbWUgc2VxdWVuY2VzIGFyZSBjb21wb3NlZCBvZiBtYW55IGdhcHMKIyBMZXQncyByZW1vdmUgZ2Vub21lcy9zZXF1ZW5jZXMgd2hlcmUgdGhlcmUgYXJlIG1vcmUgdGhhbiBYJSBnYXBzIChsZXQncyBzZXQgMTAlIGZvciBleGFtcGxlKSBpbiB0aGUgbnVjbGVvY2Fwc2lkIHByb3RlaW4sIGFzIHdlbGwgYXMgc2VxdWVuY2VzIHdpdGggbm9uLWh1bWFuIGhvc3RzCgpjaGVja19nYXBzIDwtIGZ1bmN0aW9uKHNlcSwgdGhyZXNob2xkKSB7CiAgb3V0IDwtIGFscGhhYmV0RnJlcXVlbmN5KG15bXNhW1tzZXFdXSlbWyItIl1dLzQxOSA+IHRocmVzaG9sZAogIG91dAp9Cm15cmVzIDwtIGxhcHBseSgxOjMyNzAsIGNoZWNrX2dhcHMsIHRocmVzaG9sZD0wLjEpICAjIyBjaGFuZ2UgZ2FwIHRocmVzaG9sZCBoZXJlCiAgICAgICAKcGFzcyA8LSB3aGljaChzYXBwbHkobXlyZXMsIGZ1bmN0aW9uKHgpICFpc1RSVUUoeCkpKSAgIyMgdGhlc2UgYXJlIHNlcXVlbmNlcyBwYXNzaW5nIG91ciBnYXAgdGhyZXNob2xkCm15bXNhIDwtIG15bXNhW3Bhc3NdICAjIyB1cGRhdGUgb3VyIE1TQSwgcmVtb3ZpbmcgaGlnaGx5IGdhcHBlZCBzZXF1ZW5jZXMKbXltc2EgPC0gbXltc2Fbc2V0ZGlmZihuYW1lcyhteW1zYSksIG5vbmh1bWFuX3N0cmFpbnMpXSAgIyMgcmVtb3ZlIG5vbi1odW1hbiBzZXF1ZW5jZXMKZ2Vub21lc19wcmVzZW50X21zYSA8LSBuYW1lcyhteW1zYSkgICMjIGdldCBhIGxpc3Qgb2YgYWxsIGdlbm9tZXMgaW4gb3VyIGZpbHRlcmVkIE1TQQoKIyBDaGVjayBob3cgbWFueSBzZXF1ZW5jZXMgd2UgaGF2ZSBpbiBvdXIgZmlsdGVyZWQgZGF0YXNldApsZW5ndGgoZ2Vub21lc19wcmVzZW50X21zYSkKCiMgU2V0IGFjY2Vzc2lvbiBvZiBXdWhhbi1IdS0xIGFzIHJlZmVyZW5jZSBzZXF1ZW5jZSBpbiBNU0EKcmVmIDwtIG15bXNhWyJNTjkwODk0NyJdCgojIEdlbmVyYXRlIGEgZmlsdGVyZWQgbWV0YWRhdGEgZGYgZnJvbSB0aGUgb3JpZ2luYWwsIHdoaWNoIHdlIHdpbGwgYmUgdXNpbmcgbGF0ZXIgb24KbWV0YWRhdGFfZmlsdGVyZWQgPC0gc3Vic2FtcGxlZCAlPiUgCiAgZmlsdGVyKGdlbmJhbmtfYWNjZXNzaW9uICVpbiUgZ2Vub21lc19wcmVzZW50X21zYSkKaGVhZChzZWxlY3QobWV0YWRhdGFfZmlsdGVyZWQsIHN0cmFpbiwgZ2VuYmFua19hY2Nlc3Npb24sIE5leHRzdHJhaW5fY2xhZGUsIHBhbmdvX2xpbmVhZ2UpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAojIExldCdzIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB2YXJpYW50IGdlbm9tZXMgaW4gb3VyIGZpbHRlcmVkIGRhdGFzZXQKbWV0YWRhdGFfZmlsdGVyZWQgJT4lIAogIGdyb3VwX2J5KE5leHRzdHJhaW5fY2xhZGUpICU+JQogIHN1bW1hcml6ZShjb3VudF9nZW5vbWVzPW5fZGlzdGluY3QoZ2VuYmFua19hY2Nlc3Npb24pKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXJlb3JkZXIoTmV4dHN0cmFpbl9jbGFkZSwgY291bnRfZ2Vub21lcyksIHk9Y291bnRfZ2Vub21lcykpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGZpbGw9ImRhcmsgcmVkIikgKwogIGdlb21fdGV4dChhZXMobGFiZWw9Y291bnRfZ2Vub21lcyksIGhqdXN0PTAsIHNpemU9Mi44KSArCiAgeGxhYigiVmFyaWFudCIpICsKICB5bGFiKCJOdW1iZXIgb2Ygc2VxdWVuY2VzIikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgY29vcmRfZmxpcCgpCgojIERlZmluZSBkb21haW5zIHdpdGhpbiB0aGUgbnVjbGVvY2Fwc2lkIHRvIGhlbHAgdXMgaWRlbnRpZnkgd2hlcmUgbXV0YXRpb25zIGFyZSBvY2N1cnJpbmcKIyBEb21haW4gcG9zaXRpb25zIG9idGFpbmVkIGZyb20gWWFuZyBldCBhbC4gKDIwMjEpIAojIGh0dHBzOi8vd3d3LmZyb250aWVyc2luLm9yZy9hcnRpY2xlcy8xMC4zMzg5L2ZjaGVtLjIwMjAuNjI0NzY1L2Z1bGwKZG9tYWluc19kZiA8LSBkYXRhLmZyYW1lKGMoMTo0MTkpKQpuYW1lcyhkb21haW5zX2RmKSA8LSAibXNhX3Bvc2l0aW9uIgpkb21haW5zX2RmIDwtIGRvbWFpbnNfZGYgJT4lCiAgbXV0YXRlKGRvbWFpbiA9IGNhc2Vfd2hlbihtc2FfcG9zaXRpb24+PTEgJiBtc2FfcG9zaXRpb248PTQwIH4gIk5URCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtc2FfcG9zaXRpb24+PTQxICYgbXNhX3Bvc2l0aW9uPD0xNzMgfiAiUkJEIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1zYV9wb3NpdGlvbj49MTc0ICYgbXNhX3Bvc2l0aW9uPD0yNDkgfiAiTElOSyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtc2FfcG9zaXRpb24+PTI1MCAmIG1zYV9wb3NpdGlvbjw9MzY0IH4gImRpbWVyaXphdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtc2FfcG9zaXRpb24+PTM2NSB+ICJDVEQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJOQSIpKQpkb21haW5zIDwtIGMoIk5URCIsICJSQkQiLCAiTElOSyIsICJkaW1lcml6YXRpb24iLCAiQ1REIikKYGBgCjxicj4KPGJyPgoKIyMjIyBHZXQgZnJlcXVlbmNpZXMgb2YgYWxsIGdlbm90eXBlcyAocGVyIGNvbHVtbiBzdW1tYXJ5KSBmb3IgZWFjaCB2YXJpYW50IHJlbGF0aXZlIHRvIFd1aGFuLUh1LTEKYGBge3Igd2FybmluZz1GQUxTRX0KIyBMZXQncyB0cmFuc2ZlciB0aGUgcmVmZXJlbmNlIHNlcXVlbmNlIGluZm8gaW50byBhIGRhdGEgZnJhbWUKcmVmX2RmIDwtIGFzLm1hdHJpeChteW1zYVsiTU45MDg5NDciXSkgJT4lIHQoKQpyZWZfZGYgPC0gcmVmX2RmWywgMV0gJT4lIAogIGFzLmRhdGEuZnJhbWUoY2hlY2submFtZXM9RikgJT4lIAogIHJvd2lkX3RvX2NvbHVtbih2YXI9Im1zYV9wb3NpdGlvbiIpICU+JQogIGRwbHlyOjpyZW5hbWUoInJlc2lkdWVfcmVmZXJlbmNlIiA9ICIuIikKCgojIEZ1bmN0aW9uIHRvIGlkZW50aWZ5IG1ham9yIHJlc2lkdWVzIHBlciBNU0EgY29sdW1uIHBlciBsaW5lYWdlCmdldF9nZW5vdHlwZXMgPC0gZnVuY3Rpb24obXl2YXJpYW50LCBteWRmKSB7CiAgZ2Vub21lcyA8LSBmaWx0ZXIobWV0YWRhdGFfZmlsdGVyZWQsIE5leHRzdHJhaW5fY2xhZGU9PW15dmFyaWFudCkgJT4lIHB1bGwoZ2VuYmFua19hY2Nlc3Npb24pCiAgc3ViYWxuIDwtIG15bXNhW2dlbm9tZXNdICMjZmlsdGVyIHRvIG9ubHkgc2VxdWVuY2VzIHByZXNlbnQgaW4gTVNBCiAgbmNvbHMgPC0gbGVuZ3RoKHN1YmFsbltbMV1dKQogIG1hdCA8LSBhcy5tYXRyaXgoc3ViYWxuKSAgIyMgY29udmVydCBNU0EgaW50byBtYXRyaXgKIAogIAogICMjIFdlIHdpbGwgdXNlIGEgbmVzdGVkIGZ1bmN0aW9uIGhlcmUgdG8gZ2V0IHN0YXRzIG9mIGFsbCBNU0EgY29sdW1ucwogICMjIFRoaXMgcGFydCBuZWVkcyB0byBiZSBvcHRpbWl6ZWQKICBnZXRfY29sc3RhdHMgPC0gZnVuY3Rpb24obXlwb3MsIG15c3ViYWxuKSB7CiAgICB0YWJsZSA8LSBwcm9wLnRhYmxlKHRhYmxlKG1hdFssIG15cG9zXSkpICU+JQogICAgICByb3VuZChkaWdpdHM9MykgJT4lIAogICAgICBhcy5kYXRhLmZyYW1lLnRhYmxlKCkgJT4lIAogICAgICBkcGx5cjo6cmVuYW1lKHJlc2lkdWU9VmFyMSwgZnJlcT1GcmVxKSAlPiUKICAgICAgbXV0YXRlKG1zYV9wb3NpdGlvbj1teXBvcywgdmFyaWFudD1teXZhcmlhbnQpICU+JQogICAgICBsZWZ0X2pvaW4oZG9tYWluc19kZiwgYnk9Im1zYV9wb3NpdGlvbiIpICU+JSAKICAgICAgbGVmdF9qb2luKHJlZl9kZiwgYnk9Im1zYV9wb3NpdGlvbiIpICU+JSAKICAgICAgcmVsb2NhdGUobXNhX3Bvc2l0aW9uKQogICAgdGFibGUKICB9CiAgCiAgcmVzdWx0cyA8LSByYmluZGxpc3QobGFwcGx5KDE6bmNvbHMsIGdldF9jb2xzdGF0cywgbXlzdWJhbG49c3ViYWxuKSkKICByZXN1bHRzCn0KCm15Y29sc3RhdHNfZGYgPC0gcmJpbmRsaXN0KGxhcHBseShuZXh0c3RyYWluX2NsYWRlcywgZ2V0X2dlbm90eXBlcywgbXlkZj1tZXRhZGF0YV9maWx0ZXJlZCkpCm15Y29sc3RhdHNfZGYkZG9tYWluIDwtIGZhY3RvcihteWNvbHN0YXRzX2RmJGRvbWFpbiwgbGV2ZWxzPWRvbWFpbnMpCgpgYGAKCjxicj4KPGJyPgoKIyMjIyBOb3cgdGhhdCB3ZSBoYXZlIG91ciBpbmZvcm1hdGlvbiBpbiBhIGRhdGEgZnJhbWUsIHdlIGNhbiBtYW5pcHVsYXRlIHRoaXMgdG8gaGVscCB1cyBpbiBtYWtpbmcgc29tZSBwbG90cwpgYGB7ciBnbG9iYWwtdmlldywgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDMuOH0KIyAgICBMZXQncyBtYWtlIGEgbmV3IGRhdGEgZnJhbWUgZnJvbSB0aGF0IHdpdGggdGhlIGZvbGxvd2luZyBpbmZvcm1hdGlvbjoKIyAgICAgIGkpICAgIHdoZXRoZXIgdGhlIHJlc2lkdWUgaXMgYSBtdXRhdGlvbgojICAgICAgaWkpICAgd2hldGhlciB3ZSB3YW50IHRvIGtlZXAgdGhlIHJlc2lkdWUgZm9yIHBsb3R0aW5nIChzZXQgZnJlcSB0aHJlc2hvbGQgaGVyZSwgZWcuIHByZXNlbnQgaW4gYXQgbGVhc3QgMiUgb2YgZ2Vub21lIHBvcHVsYXRpb24tIGFwcGxpZWQgYnV0IG5vdCB1c2VkIGZvciBub3cpCiMgICAgICBpaWkpICB0aGUgcHJvcG9ydGlvbiBvZiBnZW5vbWVzIHdpdGggbXV0YXRpb25zIGZvciBhIGdpdmVuIHZhcmlhbnQgcG9wdWxhdGlvbgoKdGhyZXNob2xkIDwtIDAuMDIKbXljb2xzdGF0c19kZiA8LSBteWNvbHN0YXRzX2RmICU+JSAKICBtdXRhdGUobXV0YXRpb24gPSBjYXNlX3doZW4ocmVzaWR1ZSA9PSAiLSIgfiAiTkEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzaWR1ZSA9PSByZXNpZHVlX3JlZmVyZW5jZSB+ICJubyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gInllcyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZWVwID0gY2FzZV93aGVuKGZyZXEgPj10aHJlc2hvbGQgJiBtdXRhdGlvbiA9PSAieWVzIiB+ICJ5ZXMiLCBUUlVFIH4gIm5vIikpCgoKcmVzaWR1ZXNfZGYgPC0gbXljb2xzdGF0c19kZiAlPiUgCiAgZ3JvdXBfYnkodmFyaWFudCwgbXNhX3Bvc2l0aW9uLCBkb21haW4pICU+JSAKICBzdW1tYXJpemUocHJvcF91bmNoYW5nZWQ9c3VtKGZyZXFbbXV0YXRpb249PSJubyJdKSwgCiAgICAgICAgICAgIHByb3BfbWlzc2luZz1zdW0oZnJlcVttdXRhdGlvbj09Ik5BIl0pLCAKICAgICAgICAgICAgcHJvcF9tdXRhdGlvbj0xLXByb3BfdW5jaGFuZ2VkLXByb3BfbWlzc2luZykKCiAgICAKIyBXZSBjYW4gbm93IHZpc3VhbGl6ZSB3aGVyZSBtdXRhdGlvbnMgYXJlIG9jY3VycmluZyBmb3IgdGhlIGRpZmZlcmVudCB2YXJpYW50cwpyZXNpZHVlc19kZiAlPiUgCiAgZ2dwbG90KGFlcyh4PW1zYV9wb3NpdGlvbiwgeT1wcm9wX211dGF0aW9uKSkgKyAKICBnZW9tX3BvaW50KHNpemU9MC41LCBhZXMoY29sb3I9ZG9tYWluKSkgKwogIHNjYWxlX2ZpbGxfbnBnKCkgKwogIHNjYWxlX2NvbG9yX25wZygpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9dGhyZXNob2xkLCBsaW5ldHlwZT0idHdvZGFzaCIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9Yyg0MCwgMTczLCAyNDksIDM2NCwgNDE5KSwgbGluZXR5cGU9ImRvdGRhc2giLCBjb2xvcj0iZ3JheSIpICsKICB4bGFiKCJSZXNpZHVlIChwb3NpdGlvbikiKSArCiAgeWxhYigiUHJvcG9ydGlvbiBvZiBnZW5vbWVzIHdpdGggbXV0YXRpb25zIikgKwogIHRoZW1lKGF4aXMudGV4dD1lbGVtZW50X3RleHQoc2l6ZT04KSwgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIHBsb3QudGl0bGU9ZWxlbWVudF90ZXh0KGhqdXN0PTAuNSkpICsKICBmYWNldF93cmFwKC5+dmFyaWFudCwgbnJvdz02KSArCiAgZ2d0aXRsZSgiTXV0YXRpb24gZnJlcXVlbmNpZXMgYWNyb3NzIG51Y2xlb2NhcHNpZCBkb21haW5zIG9mIFNBUlMtQ29WLTIgdmFyaWFudHMiKQoKIyBJbiB0aGUgcGxvdCBhYm92ZSwgdGhlIHgtYXhpcyBpcyB0aGUgcmVzaWR1ZS9wb3NpdGlvbiBpbiB0aGUgTVNBIGFuZCB0aGUgeS1heGlzIGlzIHRoZSBwcm9wb3J0aW9uIG9mIGdlbm9tZXMgd2l0aGluIGEgdmFyaWFudCB3aXRoIG11dGF0aW9ucyAoY29tcGFyZWQgdG8gV3VoYW4tSHUtMSkKCiMgV2hhdCBhYm91dCB0aGUgZG9tYWlucyB3aGVyZSBtdXRhdGlvbnMgbW9zdCBjb21tb25seSBvY2N1cj8gVGhpcyBpcyBhIHNpbXBsaWZpZWQgdmlldyBvZiB0aGUgcHJldmlvdXMgcGxvdApkb21haW5fbXV0YXRpb25zX2RmIDwtIHJlc2lkdWVzX2RmICU+JQogIGdyb3VwX2J5KHZhcmlhbnQsIGRvbWFpbikgJT4lIAogIHN1bW1hcml6ZShub19tdXRhdGlvbnM9bl9kaXN0aW5jdChtc2FfcG9zaXRpb25bcHJvcF9tdXRhdGlvbjx0aHJlc2hvbGRdKS9uKCksIHdpdGhfbXV0YXRpb25zPTEtbm9fbXV0YXRpb25zKSAlPiUKICByZXNoYXBlMjo6bWVsdChpZC52YXJzPWMoInZhcmlhbnQiLCAiZG9tYWluIiksIHZhbHVlLm5hbWU9InByb3AiKSAKICAKZG9tYWluX211dGF0aW9uc19kZiAlPiUKICBnZ3Bsb3QoYWVzKHg9ZG9tYWluLCB5PXByb3ApKSArIAogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgYWVzKGZpbGw9dmFyaWFibGUpKSArIAogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjRDJGMEVFIiwgIiNEMjFGM0MiKSwgbmFtZT0iIikgKyAKICB0aGVtZShheGlzLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9OCksIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCBwbG90LnRpdGxlPWVsZW1lbnRfdGV4dChoanVzdD0wLjUpKSArCiAgc2NhbGVfeF9kaXNjcmV0ZShsaW1pdHMgPSByZXYpICsKICB4bGFiKCIiKSArCiAgeWxhYigiUHJvcG9ydGlvbiBvZiBnZW5vbWVzIikgKwogIGZhY2V0X3dyYXAoLn52YXJpYW50LCBucm93PTYpICsKICBjb29yZF9mbGlwKCkgKwogIGdndGl0bGUoIk11dGF0aW9uIGZyZXF1ZW5jaWVzIGFjcm9zcyBudWNsZW9jYXBzaWQgZG9tYWlucyBvZiBTQVJTLUNvVi0yIHZhcmlhbnRzIikKCiMgRm9yIGEgbGlzdCBvZiBwb3NpdGlvbnMgYW5kIG11dGF0aW9uIGZyZXF1ZW5jaWVzIHBsZWFzZSBzZWUgRXhjZWwgc2hlZXQKCmBgYAoKPGJyPgo8YnI+CgojIyMjIExldCdzIGdldCBhbiBpZGVhIG9mIHdoYXQgbXV0YXRpb25zIGFyZSBwcmVzZW50IChjb25zaWRlcmluZyBvbmx5IHRoZSBjb25zZW5zdXMgc2VxdWVuY2UgZm9yIGVhY2ggdmFyaWFudCkKYGBge3IgcGxvdC1jb25zZW5zdXN9CiMgTGV0J3MgbG9vayBhdCBvbmx5IHBvc2l0aW9ucyB3aXRoIG11dGF0aW9ucwojIEZpcnN0IGxldCdzIGdldCBhIE1TQSBjb25zZW5zdXMgc2VxdWVuY2UgZm9yIGVhY2ggdmFyaWFudAoKZ2V0X2NvbnNlcSA8LSBmdW5jdGlvbihteXZhcmlhbnQpIHsKICAjIGZpcnN0IGZpbHRlciBNU0EgYnkgc2VsZWN0aW5nIGdlbm9tZXMgY29ycmVzcG9uZGluZyB0byB2YXJpYW50CiAgZ2Vub21lcyA8LSBmaWx0ZXIobWV0YWRhdGFfZmlsdGVyZWQsIE5leHRzdHJhaW5fY2xhZGU9PW15dmFyaWFudCkgJT4lIHB1bGwoZ2VuYmFua19hY2Nlc3Npb24pCiAgYWxua2VlcCA8LSBBQU11bHRpcGxlQWxpZ25tZW50KG15bXNhW2dlbm9tZXNdKQogIGNvbnNlcSA8LSBzdHJzcGxpdChtc2FDb25zZW5zdXNTZXF1ZW5jZShhbG5rZWVwLCBpZ25vcmVHYXBzPVQpLCAiIikgJT4lCiAgICB1bmxpc3QoKSAlPiUgCiAgICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogICAgbXV0YXRlKHZhcmlhbnQ9bXl2YXJpYW50KSAlPiUKICAgIGRwbHlyOjpyZW5hbWUoInJlc2lkdWUiID0gIi4iKSAlPiUgCiAgICByb3dpZF90b19jb2x1bW4odmFyPSJtc2FfcG9zaXRpb24iKQogIGNvbnNlcQp9Cgpjb25zZXFfZGYgPC0gcmJpbmRsaXN0KGxhcHBseShuZXh0c3RyYWluX2NsYWRlcywgZ2V0X2NvbnNlcSkpCmNvbnNlcV9kZiA8LSBjb25zZXFfZGYgJT4lCiAgbGVmdF9qb2luKHJlZl9kZikgJT4lCiAgbGVmdF9qb2luKGRvbWFpbnNfZGYpICU+JSAKICBtdXRhdGUobXV0YXRpb24gPSBjYXNlX3doZW4ocmVzaWR1ZSA9PSAiLSIgfiAiTkEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXNpZHVlICE9IHJlc2lkdWVfcmVmZXJlbmNlIH4gInllcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAibm8iKSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgIAojIE9ubHkgcGxvdCBwb3NpdGlvbnMgd2l0aCBtdXRhdGlvbnMKcG9zaXRpb25zMnBsb3QgPC0gY29uc2VxX2RmICU+JSAKICBmaWx0ZXIobXV0YXRpb249PSJ5ZXMiIHwgbXV0YXRpb249PSJOQSIpICU+JSAKICBwdWxsKG1zYV9wb3NpdGlvbikKCiMgU2V0IG1zYSBwb3NpdGlvbiBjb2x1bW4gYXMgZmFjdG9ycyAgICAgICAgICAgICAgICAgICAgICAgICAgIApjb25zZXFfZGYkbXNhX3Bvc2l0aW9uIDwtIGZhY3Rvcihjb25zZXFfZGYkbXNhX3Bvc2l0aW9uLCBsZXZlbHM9dW5pcXVlKGNvbnNlcV9kZiRtc2FfcG9zaXRpb24pKQpjb25zZXFfZGYkbXV0YXRpb24gPC0gZmFjdG9yKGNvbnNlcV9kZiRtdXRhdGlvbiwgbGV2ZWxzPWMoInllcyIsICJubyIsICJOQSIpKQpjb25zZXFfZGYkZG9tYWluIDwtIGZhY3Rvcihjb25zZXFfZGYkZG9tYWluLCBsZXZlbHM9ZG9tYWlucykKCiMgUGxvdApjb25zZXFfZGYgJT4lIAogIGZpbHRlcihtc2FfcG9zaXRpb24gJWluJSBwb3NpdGlvbnMycGxvdCkgJT4lIAogIGdncGxvdChhZXMoeD1tc2FfcG9zaXRpb24sIHk9dmFyaWFudCwgZmlsbD1tdXRhdGlvbikpICsKICBnZW9tX3RpbGUoY29sb3I9IndoaXRlIiwgc2l6ZT0wLjIpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsPXJlc2lkdWUpLCBzaXplPTIuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjRDIxRjNDIiwgIiNEMkYwRUUiLCIjRDRENEQ0IikpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9OTApLCBwbG90LnRpdGxlPWVsZW1lbnRfdGV4dChoanVzdD0wLjUpKSArIAogIHhsYWIoIlJlc2lkdWUgcG9zaXRpb24iKSArCiAgeWxhYigiVmFyaWFudCIpICsKICBmYWNldF93cmFwKC5+ZG9tYWluLCBucm93PTEsIHNjYWxlcz0iZnJlZV94IikgKwogIGdndGl0bGUoIk11dGF0aW9ucyBpbiBudWNsZW9jYXBzaWQgYWNyb3NzIGNvbnNlbnN1cyBzZXF1ZW5jZSBvZiBTQVJTLUNvVi0yIHZhcmlhbnRzIikKCiMgVGhlIGRpbWVyaXphdGlvbiBkb21haW4gZG9lcyBub3QgaGF2ZSBtdXRhdGlvbnMgYWNjb3JkaW5nIHRvIHRoZSAnY29uc2Vuc3VzIHNlcXVlbmNlJyBvZiBlYWNoIHZhcmlhbnQKCmBgYAoKPGJyPgo8YnI+CgojIyMjIE5leHQgbGV0J3MgbG9vayBhdCBpbmRpdmlkdWFsIGdlbm90eXBlcyBmb3Igb3VyIHZhcmlhbnRzIG9mIGludGVyZXN0CmBgYHtyIHBsb3QtZ2Vub3R5cGVzLCB3YXJuaW5ncz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDN9CiMgV2UgY2FuIGRlZmluZSB3aGljaCB2YXJpYW50IHdlIHdhbnQgdG8gbG9vayBhdCAoZWcuIFZPQ3Mgb3IgYWxsIHZhcmlhbnRzIHdpdGggYXQgbGVhc3QgMTAgZ2Vub21lIHJlcHJlc2VudGF0aXZlcykKIyBMZXQncyBsb29rIGF0IHRoZSBhbHBoYSwgYmV0YSwgZ2FtbWEsIGRlbHRhLCBhbmQgb21pY3JvbiB2YXJpYW50cyBmb3Igbm93CgpwbG90X2dlbm90eXBlX211dGF0aW9ucyA8LSBmdW5jdGlvbihteXZhcmlhbnQpIHsKICBnZW5vbWVzIDwtIGZpbHRlcihtZXRhZGF0YV9maWx0ZXJlZCwgTmV4dHN0cmFpbl9jbGFkZT09bXl2YXJpYW50KSAlPiUgcHVsbChnZW5iYW5rX2FjY2Vzc2lvbikKICBhbG5rZWVwIDwtIGFzLm1hdHJpeCh1bmlxdWUobXltc2FbZ2Vub21lc10pKSAlPiUKICAgIHQoKSAlPiUgCiAgICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogICAgcm93aWRfdG9fY29sdW1uKHZhcj0ibXNhX3Bvc2l0aW9uIikKICAKICAjIGZvcmNlIHJlZmVyZW5jZSBzZXF1ZW5jZSB0byBiZSBpbiBkYXRhc2V0CiAgYWxucmVmIDwtIGFzLm1hdHJpeChteW1zYVsiTU45MDg5NDciXSkgJT4lCiAgICB0KCkgJT4lIAogICAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICAgIHJvd2lkX3RvX2NvbHVtbih2YXI9Im1zYV9wb3NpdGlvbiIpCiAgCiAgIyBhZGQgcmVmIG9ubHkgaWYgaXQncyBub3QgYWxyZWFkeSBwcmVzZW50CiAgaWYgKCEiTU45MDg5NDciICVpbiUgbmFtZXMoYWxua2VlcCkpIHsKICAgIG15YWxuIDwtIGxlZnRfam9pbihhbG5rZWVwLCBhbG5yZWYsIGJ5PSJtc2FfcG9zaXRpb24iKQogIH0gZWxzZSB7CiAgICBteWFsbiA8LSBhbG5rZWVwCiAgfQogIAogIG15cGxvdGRmIDwtIG15YWxuICU+JSAKICAgICNmaWx0ZXIobXNhX3Bvc2l0aW9uICVpbiUgcG9zaXRpb25zMnBsb3QpICU+JSAKICAgIHJlc2hhcGUyOjptZWx0KGlkLnZhcj0ibXNhX3Bvc2l0aW9uIiwgdmFyaWFibGUubmFtZT0iZ2Vub21lIiwgdmFsdWUubmFtZT0icmVzaWR1ZSIpICU+JSAKICAgIGxlZnRfam9pbihyZWZfZGYsIGJ5PSJtc2FfcG9zaXRpb24iKSAlPiUKICAgIGxlZnRfam9pbihkb21haW5zX2RmLCBieT0ibXNhX3Bvc2l0aW9uIikgJT4lCiAgICBtdXRhdGUobXV0YXRpb249Y2FzZV93aGVuKHJlc2lkdWU9PSItIiB+ICJOQSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlc2lkdWUhPXJlc2lkdWVfcmVmZXJlbmNlIH4gInllcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlc2lkdWU9PXJlc2lkdWVfcmVmZXJlbmNlIH4gIm5vIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJlcnJvciIpKSAKICAKICAjIFJlbmFtZSByZWZlcmVuY2Ugc2VxdWVuY2UKICBteXBsb3RkZiRnZW5vbWUgPC0gZ3N1YigiTU45MDg5NDciLCAiV3VoYW4tSHUtMSIsIG15cGxvdGRmJGdlbm9tZSkKICAKICBteXBsb3RkZiRkb21haW4gPC0gZmFjdG9yKG15cGxvdGRmJGRvbWFpbiwgbGV2ZWxzPWRvbWFpbnMpCiAgbXlwbG90ZGYkbXNhX3Bvc2l0aW9uIDwtIGZhY3RvcihteXBsb3RkZiRtc2FfcG9zaXRpb24sIGxldmVscz11bmlxdWUobXlwbG90ZGYkbXNhX3Bvc2l0aW9uKSkKICBteXBsb3RkZiRtdXRhdGlvbiA8LSBmYWN0b3IobXlwbG90ZGYkbXV0YXRpb24sIGxldmVscz1jKCJ5ZXMiLCAibm8iLCAiTkEiKSkKICAKICBwb3NpdGlvbnMycGxvdCA8LSBteXBsb3RkZiAlPiUgCiAgICBmaWx0ZXIobXV0YXRpb249PSJ5ZXMiIHwgbXV0YXRpb249PSJOQSIpICU+JSAKICAgIGRpc3RpbmN0KG1zYV9wb3NpdGlvbikgJT4lIAogICAgcHVsbChtc2FfcG9zaXRpb24pCiAgCiAgbXlwbG90IDwtIG15cGxvdGRmICU+JQogICAgZmlsdGVyKG1zYV9wb3NpdGlvbiAlaW4lIHBvc2l0aW9uczJwbG90KSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHg9bXNhX3Bvc2l0aW9uLCB5PWdlbm9tZSwgZmlsbD1tdXRhdGlvbikpICsKICAgIGdlb21fdGlsZShjb2xvcj0id2hpdGUiLCBzaXplPTAuMikgKwogICAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1yZXNpZHVlKSwgc2l6ZT0yLjUpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjRDIxRjNDIiwgIiNEMkYwRUUiLCIjRDRENEQ0IikpICsKICAgIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChhbmdsZT05MCwgc2l6ZT03KSwgcGxvdC50aXRsZT1lbGVtZW50X3RleHQoaGp1c3Q9MC41KSwgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9NSkpICsgCiAgICB4bGFiKCJSZXNpZHVlIHBvc2l0aW9uIikgKwogICAgeWxhYigiR2Vub21lIikgKwogICAgZmFjZXRfd3JhcCgufmRvbWFpbiwgbnJvdz0xLCBzY2FsZXM9ImZyZWVfeCIpICsKICAgIGdndGl0bGUocGFzdGUobXl2YXJpYW50LCAiIGdlbm90eXBlcyIsIHNlcD0iIikpCiAgCiAgbXlwbG90CiAKfQoKIyBSdW4gZnVuY3Rpb24gb24gb3VyIHZhcmlhbnRzIG9mIGludGVyZXN0CnZvaSA8LSBjKCIyMEkgKEFscGhhLCBWMSkiLCAiMjBIIChCZXRhLCBWMikiLCAiMjBKIChHYW1tYSwgVjMpIiwgIjIxQSAoRGVsdGEpIiwgIjIxSSAoRGVsdGEpIiwgIjIxSyAoT21pY3JvbikiKQpnZW5vdHlwZV9wbG90cyA8LSBsYXBwbHkodm9pLCBwbG90X2dlbm90eXBlX211dGF0aW9ucykKZ2Vub3R5cGVfcGxvdHNbWzFdXQpnZW5vdHlwZV9wbG90c1tbMl1dICsgZ2Vub3R5cGVfcGxvdHNbWzNdXSAKZ2Vub3R5cGVfcGxvdHNbWzRdXQpnZW5vdHlwZV9wbG90c1tbNV1dIApnZW5vdHlwZV9wbG90c1tbNl1dCgpgYGAKCjxicj4KPGJyPgoKIyMjIyBXcml0ZSBtdXRhdGlvbnMgdG8gYSBmaWxlCmBgYHtyIHdyaXRlLW11dGF0aW9ucywgbWVzc2FnZT1GQUxTRX0KcHJpbnRfbXV0YXRpb25zIDwtIGZ1bmN0aW9uKG15dmFyaWFudCwgbXlvcHRpb24pIHsKICBpZiAobXl2YXJpYW50ID09ICJhbGwiKSB7CiAgICBzdWJhbG4gPC0gbXltc2EKICAgIAogIH0gZWxzZSB7CiAgICBnZW5vbWVzIDwtIGZpbHRlcihtZXRhZGF0YV9maWx0ZXJlZCwgTmV4dHN0cmFpbl9jbGFkZT09bXl2YXJpYW50KSAlPiUgcHVsbChnZW5iYW5rX2FjY2Vzc2lvbikKICAgIHN1YmFsbiA8LSBteW1zYVtnZW5vbWVzXSAjI2ZpbHRlciB0byBvbmx5IHNlcXVlbmNlcyBwcmVzZW50IGluIE1TQQogIH0KIAogIG5jb2xzIDwtIGxlbmd0aChzdWJhbG5bWzFdXSkKICBtYXQgPC0gYXMubWF0cml4KHN1YmFsbikgICMjIGNvbnZlcnQgTVNBIGludG8gbWF0cml4CiAgCiAgIyMgVGhpcyBwYXJ0IG5lZWRzIHRvIGJlIG9wdGltaXplZAogIGdldF9jb2xzdGF0cyA8LSBmdW5jdGlvbihteXBvcywgbXlzdWJhbG4pIHsKICAgIHRhYmxlIDwtIHRhYmxlKG1hdFssIG15cG9zXSkgJT4lCiAgICAgIGFzLmRhdGEuZnJhbWUudGFibGUoKSAlPiUgCiAgICAgIGRwbHlyOjpyZW5hbWUocmVzaWR1ZT1WYXIxLCBmcmVxPUZyZXEpICU+JQogICAgICBtdXRhdGUobXNhX3Bvc2l0aW9uPW15cG9zLCB2YXJpYW50PW15dmFyaWFudCkgJT4lCiAgICAgIGxlZnRfam9pbihkb21haW5zX2RmLCBieT0ibXNhX3Bvc2l0aW9uIikgJT4lIAogICAgICBsZWZ0X2pvaW4ocmVmX2RmLCBieT0ibXNhX3Bvc2l0aW9uIikgJT4lIAogICAgICByZWxvY2F0ZShtc2FfcG9zaXRpb24pCiAgICB0YWJsZQogIH0KICAKICByZXN1bHRzIDwtIHJiaW5kbGlzdChsYXBwbHkoMTpuY29scywgZ2V0X2NvbHN0YXRzLCBteXN1YmFsbj1zdWJhbG4pKQogIAogIGlmIChteW9wdGlvbiA9PSAibm8iKSB7CiAgICByZXN1bHRzIDwtIHJlc3VsdHMgJT4lIGdyb3VwX2J5KG1zYV9wb3NpdGlvbiwgZG9tYWluKSAlPiUKICAgICAgc3VtbWFyaXplKGNvdW50X211dGF0aW9ucz1zdW0oZnJlcVtyZXNpZHVlIT1yZXNpZHVlX3JlZmVyZW5jZSAmIHJlc2lkdWUhPSItIl0pLCAKICAgICAgICAgICAgICBjb3VudF9jb25zZXJ2ZWQ9c3VtKGZyZXFbcmVzaWR1ZT09cmVzaWR1ZV9yZWZlcmVuY2UgfCByZXNpZHVlPT0iLSJdKSkgJT4lIAogICAgICBtdXRhdGUodmFyaWFudD1teXZhcmlhbnQsIHRvdGFsPXN1bShjb3VudF9tdXRhdGlvbnMsIGNvdW50X2NvbnNlcnZlZCksCiAgICAgICAgICAgICBtdXRhdGlvbl9wZXJjZW50YWdlPWNvdW50X211dGF0aW9ucy90b3RhbCoxMDAsIGNvbnNlcnZlZF9wZXJjZW50YWdlPWNvdW50X2NvbnNlcnZlZC90b3RhbCoxMDApICU+JQogICAgICBzZWxlY3QoLXRvdGFsKQogICAgCiAgfSBlbHNlIGlmIChteW9wdGlvbiA9PSAieWVzIikgewogICAgCiAgICByZXN1bHRzIDwtIHJlc3VsdHMgJT4lIGdyb3VwX2J5KG1zYV9wb3NpdGlvbiwgZG9tYWluLCByZXNpZHVlLCByZXNpZHVlX3JlZmVyZW5jZSkgJT4lCiAgICBzdW1tYXJpemUoY291bnRfbXV0YXRpb25zPXN1bShmcmVxW3Jlc2lkdWUhPXJlc2lkdWVfcmVmZXJlbmNlICYgcmVzaWR1ZSE9Ii0iXSksIAogICAgICAgICAgICAgIGNvdW50X2NvbnNlcnZlZD1zdW0oZnJlcVtyZXNpZHVlPT1yZXNpZHVlX3JlZmVyZW5jZSB8IHJlc2lkdWU9PSItIl0pKSAlPiUKICAgIHVuZ3JvdXAoKSAlPiUKICAgIGdyb3VwX2J5KG1zYV9wb3NpdGlvbiwgZG9tYWluKSAlPiUKICAgIG11dGF0ZSh2YXJpYW50PW15dmFyaWFudCwgdG90YWw9c3VtKGNvdW50X211dGF0aW9ucywgY291bnRfY29uc2VydmVkKSwgbXV0YXRpb25fcGVyY2VudGFnZT1jb3VudF9tdXRhdGlvbnMvdG90YWwqMTAwLCBjb25zZXJ2ZWRfcGVyY2VudGFnZT1jb3VudF9jb25zZXJ2ZWQvdG90YWwqMTAwKSAlPiUKICAgIHNlbGVjdCgtdG90YWwpCiAgfQoKICByZXN1bHRzCn0KCm11dGF0aW9uc192YXJpYW50X25vcmVzaWR1ZSA8LSByYmluZGxpc3QobGFwcGx5KG5leHRzdHJhaW5fY2xhZGVzLCBwcmludF9tdXRhdGlvbnMsIG15b3B0aW9uPSJubyIpKQptdXRhdGlvbnNfdmFyaWFudF93cmVzaWR1ZSA8LSByYmluZGxpc3QobGFwcGx5KG5leHRzdHJhaW5fY2xhZGVzLCBwcmludF9tdXRhdGlvbnMsIG15b3B0aW9uPSJ5ZXMiKSkKbXV0YXRpb25zX2FsbF9ub3Jlc2lkdWUgPC0gcHJpbnRfbXV0YXRpb25zKCJhbGwiLCBteW9wdGlvbj0ibm8iKQptdXRhdGlvbnNfYWxsX3dyZXNpZHVlIDwtIHByaW50X211dGF0aW9ucygiYWxsIiwgbXlvcHRpb249InllcyIpCgojIFdyaXRlIHN1bW1hcnkgdGFibGVzIHRvIHNlcGFyYXRlIHNoZWV0cyBpbiBhbiBFeGNlbCBmaWxlCndyaXRlLnhsc3gobXV0YXRpb25zX3ZhcmlhbnRfbm9yZXNpZHVlLCBmaWxlPSIuL3Jlc3VsdHMvbXV0YXRpb25zX3N1bW1hcnkueGxzeCIsIAogICAgICAgICAgIHNoZWV0TmFtZT0idmFyaWFudHNfZ2VuZXJhbCIsIAogICAgICAgICAgIGNvbE5hbWVzPVQsCiAgICAgICAgICAgcm93TmFtZXM9RiwKICAgICAgICAgICBhcHBlbmQ9RikKd3JpdGUueGxzeChtdXRhdGlvbnNfdmFyaWFudF9ub3Jlc2lkdWUsIGZpbGU9Ii4vcmVzdWx0cy9tdXRhdGlvbnNfc3VtbWFyeS54bHN4IiwgCiAgICAgICAgICAgc2hlZXROYW1lPSJ2YXJpYW50c193aXRoQUEiLAogICAgICAgICAgIGNvbE5hbWVzPVQsCiAgICAgICAgICAgcm93TmFtZXM9RiwKICAgICAgICAgICBhcHBlbmQ9VCkKd3JpdGUueGxzeChtdXRhdGlvbnNfdmFyaWFudF9ub3Jlc2lkdWUsIGZpbGU9Ii4vcmVzdWx0cy9tdXRhdGlvbnNfc3VtbWFyeS54bHN4IiwgCiAgICAgICAgICAgc2hlZXROYW1lPSJjb21iaW5lZF9nZW5lcmFsIiwgCiAgICAgICAgICAgY29sTmFtZXM9VCwKICAgICAgICAgICByb3dOYW1lcz1GLAogICAgICAgICAgIGFwcGVuZD1UKQp3cml0ZS54bHN4KG11dGF0aW9uc192YXJpYW50X25vcmVzaWR1ZSwgZmlsZT0iLi9yZXN1bHRzL211dGF0aW9uc19zdW1tYXJ5Lnhsc3giLCAKICAgICAgICAgICBzaGVldE5hbWU9ImNvbWJpbmVkX3dpdGhBQSIsIAogICAgICAgICAgIGNvbE5hbWVzPVQsCiAgICAgICAgICAgcm93TmFtZXM9RiwKICAgICAgICAgICBhcHBlbmQ9VCkKCiMgUmVtb3ZlIHVudXNlZCBkYXRhIGZyYW1lcwpybShsaXN0PWMoIm11dGF0aW9uc192YXJpYW50X25vcmVzaWR1ZSIsICJtdXRhdGlvbnNfdmFyaWFudF93cmVzaWR1ZSIsICJtdXRhdGlvbnNfYWxsX25vcmVzaWR1ZSIsICJtdXRhdGlvbnNfYWxsX3dyZXNpZHVlIikpCgpgYGAKCgo=